package fag.core.game;

import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;

import fag.core.collision.Hitbox;
import fag.core.collision.Quadtree;
import fag.core.game.Transformable.Referential;
import fag.core.geometry.Rectangle2D;

/**
 * Classe d'une scène du jeu.
 * @author lumz.
 */
public abstract class Scene {


	private List<Camera> cameras = new ArrayList<Camera>();

	private Quadtree quadtree = new Quadtree();

	private List<AbstractDoodad> doodads = new ArrayList<AbstractDoodad>();

	/**
	 * Constructeur par défault.
	 */
	public Scene() {
	}

	/**
	 * Ajouter une caméra à la scène.
	 * @param camera La caméra à ajouter.
	 */
	public void addCamera(Camera camera) {

		synchronized(cameras) {
			if(!cameras.contains(camera))
				cameras.add(camera);
		}

		camera.scene = this;
	}
	
	/**
	 * Ajoute une zone de la scène qui est à redessiner.
	 * @param region La zone de la scène qui est à redessiner.
	 */
	public void addDirtyRegion(Rectangle2D region) {

		synchronized(cameras) {
			for(Camera camera : cameras) {
				
				Rectangle2D extendedRegion = region.clone();
				extendedRegion.set(region.getX() - 2, region.getY() - 2, region.getWidth() + 4, region.getY() + 4);
				camera.addDirtyRegion(extendedRegion);
			}
		}
	}

	/**
	 * Ajouter un objet à la scène.
	 * @param doodad L'objet à ajouter.
	 */
	public void addDoodad(AbstractDoodad doodad) {

		synchronized(doodads) {
			
			if(!doodads.contains(doodad)) {
				doodads.add(doodad);
				doodad.setScene(this);
			}
		}
	}

	/**
	 * Obtenir la liste des doodads de la scène.
	 * @return La liste des doodads de la scène.
	 */
	public List<AbstractDoodad> getDoodads() {

		synchronized(doodads) {
			return new ArrayList<AbstractDoodad>(doodads);
		}
	}
	
	/**
	 * Obtenir l'ordre d'affichage d'un doodad.
	 * @param doodad Le doodad dont on aimerai connaître l'ordre d'affichage.
	 * @return Le rang d'affichage du doodad.
	 */
	public int getOrdering(AbstractDoodad doodad) {
		
		synchronized(doodads) {
			return doodads.indexOf(doodad);
		}
	}
	
	/**
	 * Accesseur de l'arbre quaternaire de la scène.
	 * @return L'arbre quaternaire de la scène.
	 */
	public Quadtree getQuadtree() {
		
		synchronized(quadtree) {
			return quadtree;
		}
	}

	/**
	 * Retirer une caméra à la scène.
	 * @param camera La caméra à retirer.
	 */
	public void removeCamera(Camera camera) {

		synchronized(cameras) {
			if(cameras.remove(camera))
				camera.scene = null;
		}
	}

	/**
	 * Retirer un Doodad à la scène.
	 * @param doodad Le Doodad à retirer.
	 */
	public void removeDoodad(AbstractDoodad doodad) {

		synchronized(doodads) {
			
			if(doodads.remove(doodad)) {
				doodad.setScene(null);
			}
		}
	}
	
	/**
	 * Modifier l'ordre d'affichage du doodad.
	 * @param doodad Le doodad dont l'ordre d'affichage doit être modifié.
	 * @param z L'ordre d'affichage, 0 étant le premier affiché.
	 */
	public void setOrdering(AbstractDoodad doodad, int z) {
		
		synchronized(doodads) {
			
			if(doodads.remove(doodad)) {
				doodads.set(z, doodad);
				// TODO : Peut être une meilleur façon de rafraichir l'écran que de faire une translation nulle
				doodad.translate(0, 0, Referential.ABSOLUTE);
			}
		}
	}
	

	/**
	 * Avancer la scène dans le temps.
	 * @param dt L'avancement.
	 */
	public void step(double dt) {

		forwardAnimations(dt);
	}
	
	/**
	 * Dessine la scène.
	 * @param graphics2d Le contexte graphique.
	 */
	void draw(Graphics2D graphics2d) {
		
		// Dessiner l'arrière-plan
		drawBackground(graphics2d);
		
		// Dessiner les doodads
		synchronized(doodads) {
			
			for(AbstractDoodad doodad : doodads)
				doodad.draw(graphics2d);
		}
		
		// Desssiner le premier-plan
		drawForeground(graphics2d);
	}
	
	/**
	 * Reconstruire l'arbre quaternaire de la scène.
	 */
	void rebuildQuadtree() {

		List<Hitbox> hitboxes = new ArrayList<Hitbox>();

		synchronized(doodads) {
			
			for(AbstractDoodad doodad : doodads)
				hitboxes.add(doodad.getHitbox());
		}

		synchronized(quadtree) {
			
			quadtree.build(hitboxes);
		}
	}
	
	/**
	 * Dessiner l'arrière-plan. C'est un point d'extension du framework.
	 * @param graphics2d Le contexte de dessin.
	 */
	abstract protected void drawBackground(Graphics2D graphics2d);
	
	/**
	 * Dessiner le premier plan. C'est un point d'extension du framework.
	 * @param graphics2d Le contexte de dessin.
	 */
	abstract protected void drawForeground(Graphics2D graphics2d);

	/**
	 * Avancer les animations de la scène.
	 * @param dt La durée de l'avancement.
	 */
	private void forwardAnimations(double dt) {

		synchronized(doodads) {
			
			for(AbstractDoodad doodad : doodads) {
				doodad.forward(dt);
			}
		}
	}
}